﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Net.Nntp.Cache
{
    using System;
    using System.Data;
    using System.Data.SqlServerCe;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>Provides the means to bulk-insert <see cref="InboundArticle"/> objects.</summary>
    internal struct InboundArticleInserter : IDisposable
    {
        // The SQL Server CE minimal date, see http://msdn.microsoft.com/en-us/library/ms172424.aspx
        private static readonly DateTime sqlServerMinimalDate = new DateTime(1753, 1, 1);

        private readonly SqlCeCommand articleCommand;
        private readonly SqlCeCommand messageIdCommand;
        private readonly int fromMaxLength;
        private readonly int subjectMaxLength;
        private readonly int messageIdMaxLength;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IDisposable.Dispose"/>.</summary>
        [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", Justification = "We *are* calling Dispose, CA bug?")]
        public void Dispose()
        {
            this.messageIdCommand.Dispose();
            this.articleCommand.Dispose();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="InboundArticleInserter"/> struct.</summary>
        /// <param name="connection">The connection to use.</param>
        internal InboundArticleInserter(SqlCeConnection connection)
        {
            using (var articleCommandGuard = ScopeGuard.Create(this.articleCommand =
                CommandHelper.Create(connection, InboundArticleNames.Table)))
            using (var messageIdCommandGuard = ScopeGuard.Create(this.messageIdCommand =
                CommandHelper.Create(connection, MessageIdNames.Table, MessageIdNames.GroupIdMessageIdIndex)))
            {
                using (var articleReader = this.articleCommand.ExecuteReader())
                using (var articleSchemaTable = articleReader.GetSchemaTable())
                {
                    this.fromMaxLength = SqlCeHelper.GetColumnSize(articleSchemaTable, InboundArticleNames.FromColumn);
                    this.subjectMaxLength = SqlCeHelper.GetColumnSize(articleSchemaTable, InboundArticleNames.SubjectColumn);
                }

                using (var messageIdReader = this.messageIdCommand.ExecuteReader())
                using (var messageIdSchemaTable = messageIdReader.GetSchemaTable())
                {
                    this.messageIdMaxLength =
                        SqlCeHelper.GetColumnSize(messageIdSchemaTable, MessageIdNames.MessageIdColumn);
                }

                articleCommandGuard.Dismiss();
                messageIdCommandGuard.Dismiss();
            }
        }

        /// <summary>Adds a new article to the database.</summary>
        /// <exception cref="ArgumentException"><paramref name="messageId"/> exceeds the maximum allowed length, or a
        /// unique constraint was violated.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="messageId"/> and/or <paramref name="subject"/>
        /// equal <c>null</c>.</exception>
        /// <remarks><paramref name="subject"/> is trimmed to at most 128 characters.</remarks>
        internal void Insert(
            Group group,
            long number,
            string messageId,
            string referencesMessageId,
            DateTime? date,
            string from,
            string subject,
            bool hasHeaderBeenReceived,
            bool receiveFullArticle,
            bool? hasAttachments)
        {
            messageId = Truncate(messageId, this.messageIdMaxLength);
            referencesMessageId = Truncate(referencesMessageId, this.messageIdMaxLength);

            if (date < sqlServerMinimalDate)
            {
                date = sqlServerMinimalDate;
            }

            from = Truncate(from, this.fromMaxLength);
            subject = Truncate(subject, this.subjectMaxLength);

            using (var transaction = new Transaction(this.articleCommand, this.messageIdCommand))
            {
                using (var articleResultSet = this.articleCommand.ExecuteResultSet(ResultSetOptions.Updatable))
                using (var messageIdResultSet = this.messageIdCommand.ExecuteResultSet(ResultSetOptions.Updatable))
                {
                    long? referencesNumber = null;
                    bool isBeingWatched = false;
                    var originalNumber = number;

                    if (referencesMessageId != null)
                    {
                        if (messageIdResultSet.SeekAndRead(DbSeekOptions.FirstEqual, group.Id, referencesMessageId))
                        {
                            referencesNumber = (long)messageIdResultSet[MessageIdNames.NumberColumn];
                            articleResultSet.SeekAndRead(DbSeekOptions.FirstEqual, group.Id, referencesNumber);
                            isBeingWatched = (bool)articleResultSet[InboundArticleNames.IsBeingWatchedColumn];
                            originalNumber = (long)articleResultSet[InboundArticleNames.OriginalNumberColumn];
                        }
                    }

                    articleResultSet.Insert(
                        InboundArticleNames.InsertableColumns,
                        group.Id,
                        number,
                        referencesNumber,
                        originalNumber,
                        date,
                        from,
                        subject,
                        hasHeaderBeenReceived,
                        false,
                        receiveFullArticle || isBeingWatched,
                        hasAttachments,
                        isBeingWatched);
                    messageIdResultSet.Insert(
                        MessageIdNames.InsertableColumns, group.Id, number, messageId);
                }

                transaction.Commit();
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static string Truncate(string value, int maxLength)
        {
            return (value != null) && (value.Length > maxLength) ? value.Substring(0, maxLength) : value;
        }
    }
}
